package com.social.utils;

import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @BelongsProject: social
 * @BelongsPackage: com.social.utils
 * @Author: zhm
 * @CreateTime: 2022-12-10  21:40
 * @Description: TODO
 * @Version: 1.0
 */
@SuppressWarnings("ALL")
public class RSAUtil {
    private static final Logger logger = LoggerFactory.getLogger(RSAUtil.class);

    static{
        try{
            Security.addProvider(new BouncyCastleProvider());
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 加密算法RSA
     */
    private static final String KEY_ALGORITHM = "RSA";

    /**
     * 算法名称/加密模式/数据填充方式
     * 默认：RSA/ECB/PKCS1Padding
     */
    private static final String ALGORITHMS = "RSA/ECB/PKCS1Padding";

    /**
     * RSA最大加密明文大小
     */
    private static final int MAX_ENCRYPT_BLOCK = 245;

    /**
     * RSA最大解密密文大小
     */
    private static final int MAX_DECRYPT_BLOCK = 256;

    /**
     * RSA 位数 如果采用2048 上面最大加密和最大解密则须填写:  245 256
     */
    private static final int INITIALIZE_LENGTH = 2048;

    /**
     * 后端RSA的密钥对(公钥和私钥)Map，由静态代码块赋值
     */
    private static Map<String, String> map = new LinkedHashMap<>(2);

    /**
     * 生成密钥对(公钥和私钥)
     * @return
     */
    public static Map<String, String> genKeyPair() throws Exception {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        keyPairGen.initialize(INITIALIZE_LENGTH);
        KeyPair keyPair = keyPairGen.generateKeyPair();
        // 获取公钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        // 获取私钥
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        // 得到公钥字符串
        String publicKeyString = Base64.encodeBase64String(publicKey.getEncoded());
        // 得到私钥字符串
        String privateKeyString = Base64.encodeBase64String((privateKey.getEncoded()));
        map.put("publicKey", publicKeyString);
        map.put("privateKey", privateKeyString);
        return map;
    }

    /**
     * RSA私钥解密
     * @param data BASE64编码过的密文
     * @param privateKey 私钥(BASE64编码)
     * @return utf-8编码的明文
     */
    public static byte[] decryptByPrivateKey(byte[] data, String privateKey) throws Exception {
        //base64格式的key字符串转Key对象
        Key privateK = KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey)));
        Cipher cipher = Cipher.getInstance(ALGORITHMS);
        cipher.init(Cipher.DECRYPT_MODE, privateK);

        //分段进行解密操作
        return encryptAndDecryptOfSubsection(data, cipher, MAX_DECRYPT_BLOCK);
    }

    /**
     * RSA公钥加密
     * @param data BASE64编码过的密文
     * @param publicKey 公钥(BASE64编码)
     * @return utf-8编码的明文
     */
    public static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Exception {
        //base64格式的key字符串转Key对象
        Key publicK = KeyFactory.getInstance(KEY_ALGORITHM).generatePublic(new X509EncodedKeySpec(Base64.decodeBase64(publicKey)));
        Cipher cipher = Cipher.getInstance(ALGORITHMS);
        cipher.init(Cipher.ENCRYPT_MODE, publicK);

        //分段进行加密操作
        return encryptAndDecryptOfSubsection(data, cipher, MAX_ENCRYPT_BLOCK);
    }

    /**
     * RSA公钥解密
     * @param data BASE64编码过的密文
     * @param publicKey RSA公钥
     * @return utf-8编码的明文
     */
    public static byte[] pubKeyDec(byte[] data, String publicKey) throws Exception {
        //base64格式的key字符串转Key对象
        Key privateK = KeyFactory.getInstance(KEY_ALGORITHM).generatePublic(new X509EncodedKeySpec(Base64.decodeBase64(publicKey)));
        Cipher cipher = Cipher.getInstance(ALGORITHMS);
        cipher.init(Cipher.DECRYPT_MODE, privateK);

        //分段进行解密操作
        return encryptAndDecryptOfSubsection(data, cipher, MAX_DECRYPT_BLOCK);
    }



    /**
     * RSA私钥加密
     * @param data 待加密的明文
     * @param privateKey RSA私钥
     * @return  经BASE64编码后的密文
     */
    public static byte[] privKeyEnc(byte[] data, String privateKey) throws Exception {

        //base64格式的key字符串转Key对象
        Key publicK = KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey)));
        Cipher cipher = Cipher.getInstance(ALGORITHMS);
        cipher.init(Cipher.ENCRYPT_MODE, publicK);

        //分段进行加密操作
        return encryptAndDecryptOfSubsection(data, cipher, MAX_ENCRYPT_BLOCK);
    }

    /**
     * 分段进行加密、解密操作
     */
    private static byte[] encryptAndDecryptOfSubsection(byte[] data, Cipher cipher, int encryptBlock) throws Exception {
        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段加密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > encryptBlock) {
                cache = cipher.doFinal(data, offSet, encryptBlock);
            } else {
                cache = cipher.doFinal(data, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i ++;
            offSet = i * encryptBlock;
        }
        out.close();
        return out.toByteArray();
    }

    /**
     * 私钥解密
     * @param content
     * @param key
     * @param privateKey
     * @return
     * @throws Exception
     */
    public String DecrypByPrivateKey(String content, String key, String privateKey) throws Exception{
        //用私钥解密到AES的key
        byte[] plaintext = decryptByPrivateKey(Base64.decodeBase64(content),privateKey);
        content = new String(plaintext);
        System.out.println("RSA 解密之后:");
        System.out.println(content);
        //AES解密得到明文data数据
        String result = AESUtil.decrypt(content, key);
        System.out.println("AES 解密之后:");
        System.out.println(result);
        return result;
    }



    public static void main(String[] args) throws Exception {
        //随机获取AES的key，加密data数据
        String key = AESUtil.getKey();
        //生成公钥和私钥
        System.out.println("key:  " + key);
        genKeyPair();

        //获取公钥和私钥
        String publicKey = (String) map.get("publicKey");
        String privateKey = (String) map.get("privateKey");
        System.out.println("publicKey:  " + publicKey);
        System.out.println("privateKey:  " + privateKey);
        System.out.println("=====================公钥加密，私钥解密==================");
        //随机AES的key加密后的密文
        String data = AESUtil.encrypt("zhm123", key);
        System.out.println("AES 加密之后:");
        System.out.println(data);
        //用公钥来加密AES的key，并转成Base64
        String aesKey = Base64.encodeBase64String(encryptByPublicKey(data.getBytes(),publicKey));
        System.out.println("RSA 加密之后:");
        System.out.println(aesKey);
        //用私钥解密到AES的key
        byte[] plaintext = decryptByPrivateKey(Base64.decodeBase64(aesKey),privateKey);
        aesKey = new String(plaintext);
        System.out.println("RSA 解密之后:");
        System.out.println(aesKey);
        //AES解密得到明文data数据
        String result = AESUtil.decrypt(aesKey, key);
        System.out.println("AES 解密之后:");
        System.out.println(result);

        System.out.println("=====================私钥加密，公钥解密==================");
        //随机AES的key加密后的密文
        String data1 = AESUtil.encrypt("你们好啊", key);
        System.out.println("AES 加密之后:");
        System.out.println(data1);
        //用私钥来加密AES的key，并转成Base64
        String aesKey1 = Base64.encodeBase64String(privKeyEnc(data1.getBytes(),privateKey));
        System.out.println("RSA 加密之后:");
        System.out.println(aesKey1);
        //用公钥解密到AES的key
        byte[] plaintext1 = pubKeyDec(Base64.decodeBase64(aesKey1),publicKey);
        aesKey1 = new String(plaintext1);
        System.out.println("RSA 解密之后:");
        System.out.println(aesKey1);
        //AES解密得到明文data数据
        String result1 = AESUtil.decrypt(aesKey1, key);
        System.out.println("AES 解密之后:");
        System.out.println(result1);

    }
}
